#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/types.h>
#include "chrono.h"
#include "conf.h"

struct timeval zero={ 0, 0 };

static struct termios stored_settings;
static int termios_set=0;

/* get an int value for the 1/100 seconds */
/* this is used so that lap value are consistent with
 * the values of the chronometer */
static struct timeval chr_round(struct timeval tv) {
    struct timeval result;
    result=tv;
    result.tv_usec /= 1e4;
    result.tv_usec *= 1e4;
    return result;
}

static struct timeval add(struct timeval tv1, struct timeval tv2) {
    struct timeval result;
    result.tv_sec=tv1.tv_sec + tv2.tv_sec;
    result.tv_usec=tv1.tv_usec + tv2.tv_usec;
    if (result.tv_usec>=1e6) {
        result.tv_sec += 1;
        result.tv_usec -= 1e6;
    }
    return result;
}

static struct timeval sub(struct timeval tv1, struct timeval tv2) {
    struct timeval result;
    if ((tv1.tv_sec>tv2.tv_sec) || ((tv1.tv_sec==tv2.tv_sec) && (tv1.tv_usec>=tv2.tv_usec))) { /* tv1 >= tv2 */
        result.tv_sec=tv1.tv_sec - tv2.tv_sec;
        result.tv_usec=tv1.tv_usec - tv2.tv_usec;
        if (result.tv_usec<0) {
            result.tv_usec += 1e6;
            result.tv_sec -= 1;
        }
    } else { /* tv1 < tv2 */
        result=sub(tv2, tv1);
        result.tv_sec=- result.tv_sec;
        result.tv_usec=- result.tv_usec;
    }
    return result;
}

static void set_keypress(void) {
    struct termios new_settings;

    if (0==termios_set) {
        tcgetattr(0, &stored_settings);
        new_settings=stored_settings;
        /* Disable canonical mode, and set buffer size to 1 byte */
        new_settings.c_lflag &= (~ICANON);
        new_settings.c_cc[VTIME]=0;
        new_settings.c_cc[VMIN]=1;
        new_settings.c_lflag &= (~ECHO);
        tcsetattr(0, TCSANOW, &new_settings);
        termios_set=1;
    }
    return;
}

static void reset_keypress(void) {
    if (1==termios_set) {
        tcsetattr(0, TCSANOW, &stored_settings);
    }
    return;
}

/* the chrono function handles all chronometer functionalities */
/* start, stop, lap, get current value and status */
int chrono(chr_command_t command, gpointer data) {
    ProgressData *pdata=(ProgressData *)data;

    switch(command) {
        case CHR_RESET :
            pdata->running=0;
            pdata->offset=zero;
            pdata->previous_lap=zero;
            pdata->initial=zero;
            pdata->tv=zero;
            pdata->pomodoro=zero;
            break;
        case CHR_LAP :
            if (!pdata->running) {
                pdata->tv=pdata->offset;
                pdata->lap=zero;
            } else {
                gettimeofday(&pdata->now, NULL);
                /* get absolute time */
                pdata->tv=add(pdata->offset, sub(pdata->now, pdata->initial));
                pdata->lap=sub(chr_round(pdata->tv), pdata->previous_lap);
                pdata->previous_lap=chr_round(pdata->tv);
            }
            break;
        case CHR_START_STOP :
            if (!pdata->running) { /* do start */
                gettimeofday(&pdata->initial, NULL);
                pdata->running=1;
                pdata->pomodoro=(struct timeval){ pdata->p_secs, 0 };
            } else { /* do stop */
                gettimeofday(&pdata->now, NULL);
                pdata->tv=add(pdata->offset, sub(pdata->now, pdata->initial));
                pdata->lap=sub(chr_round(pdata->tv), pdata->previous_lap);
                pdata->interrupt=pdata->now;
                pdata->previous_lap=chr_round(pdata->tv);
                pdata->offset=add(pdata->offset, sub(pdata->now, pdata->initial));
                pdata->running=0;
            }
            break;
        case CHR_POLL : /* get current values */
            if (!pdata->running) {
                pdata->tv=pdata->offset;
                pdata->lap=zero; 
            } else {
                gettimeofday(&pdata->now, NULL);
                pdata->tv=add(pdata->offset, sub(pdata->now, pdata->initial));
                pdata->lap=sub(chr_round(pdata->tv), pdata->previous_lap);
                pdata->pomodoro=(struct timeval){ pdata->p_secs, 0 };
                pdata->pomodoro=sub(pdata->pomodoro, pdata->tv);
            }
            break;
        case CHR_RUNNING : /* get status */
            break;
        default :
            fprintf(stderr, "Error in chrono: invalid value (%d) for command\n", command);
            pdata->tv=zero;
            pdata->lap=zero;
            break;
    }
    return pdata->running;
}

char *print_time(struct timeval tv) {
    char *buf;
    int h, m, s, ms;

    buf=malloc(255);
    bzero(buf, 255);
    ms=tv.tv_usec/10000;
    h=tv.tv_sec/3600;
    m=(tv.tv_sec - h*3600)/60;
    s=tv.tv_sec - h*3600 - m*60;
    sprintf(buf,"%02d:%02d:%02d.%02d", h, m, s, ms);

    return buf;
}

void chronometer(gpointer data) {
    char c;
    fd_set rfds;
    struct timeval tv_sel, tv, lap;
    int retval,stop=0,running,l;
    ProgressData *pdata=(ProgressData *)data;

    chrono(CHR_RESET, NULL);
    printf("chronometer\n");
    printf("Press:\tS to START/STOP\n");
    printf("\tL to LAP\n");
    printf("\tR to RESET\n");
    printf("\tQ to QUIT\n\n");
    set_keypress();
    running=chrono(CHR_START_STOP, &pdata);
    if (!running) {
        printf("%c", 13);
        print_time(tv);
        printf(" ");
        print_time(lap);
        printf(" STOP\n");
        stop=1;
    }
    while (1) {
        FD_ZERO(&rfds);
        FD_SET(STDIN_FILENO, &rfds); /* stdin */
        if (!chrono(CHR_RUNNING, NULL)) {
            /* restart only when S or s is pressed */
            retval=select(1, &rfds, NULL, NULL, NULL);
        } else {
            /* timer 10 ms */
            tv_sel.tv_sec=0;
            tv_sel.tv_usec=10000;
            retval=select(1, &rfds, NULL, NULL, &tv_sel);
        }
        if (retval>0) { /* a key was pressed */
            l=read(STDIN_FILENO, &c, 1);
            if (1==l) {
                switch (c) {
                    case 'q':
                    case 'Q':
                        printf(" QUIT\n");
                        reset_keypress();
                        exit(0);
                        break;
                    case 'l':
                    case 'L':
                        chrono(CHR_LAP, &pdata);
                        printf("%c%s %s LAP\n", 13,print_time(tv),print_time(lap));
                        break;
                    case 's':
                    case 'S':
                        running=chrono(CHR_START_STOP, &pdata);
                        if (!running) {
                            printf("%c%s %s STOP\n", 13,print_time(tv),print_time(lap));
                            stop=1;
                        }
                        break;
                    case 'r':
                    case 'R':
                        chrono(CHR_RESET, NULL);
                        printf(" RESET\n");
                        break;
                    default:
                        fprintf(stderr, "\ninvalid key %c (use s, l, q)\n", c);
                }
            } else {
                fprintf(stderr, "read error\n");
                reset_keypress();
                exit(1);
            }
        } else if (retval==0) { /* no key pressed */
            chrono(CHR_POLL, &pdata);
            printf("%c%s %s", 13,print_time(tv),print_time(lap));
        } else { /* error */
            fprintf(stderr, "select error\n");
            reset_keypress();
            exit(1);
        }

        fflush(stdout);
    }
    return;
}
